grammar Graphr;

//#### LEXER ####
options {
	output=AST;
  ASTLabelType=CommonTree;
	backtrack=true;
}

tokens { 
  FUNC; 
  CALL; 
  NUMBER; 
  HEX; 
  STRING; 
  ARG; 
  SLIST; 
  ARRAY_LIST; 
  HASH_MAP;
  GET_VAL;
  BOOL;
  AND;
  OR;
  WHILE;
  FOR;
  FOREACH;
  NOT;
  NIL = 'nil';
  T = 'true';
  F = 'false';
  PUTS = 'puts';
  READ_FILE = 'read_file';
  WRITE_FILE = 'write_file';
  CREATE_GRAPH = 'graph';
  CREATE_CHART = 'chart';
  SPLIT = 'split';
  LENGTH = 'length';
  SUBSTRING = 'substring';
  CONTAINS = 'contains';
}

@header {
  import java.util.HashMap;
  import java.util.HashMap;
}

@members {
  HashMap<String, CommonTree> functions = new HashMap<String, CommonTree>();
  ArrayList<String> filesToInclude = new ArrayList<String>();
}

fragment
LETTER	:	'A'..'Z'|'a'..'z'|'_';

fragment
HEXDIGIT :	('0'..'9'|'a'..'f'|'A'..'F');

fragment
DIGIT	:	'0'..'9';

fragment
ESCAPESEQUENCE	:	'\\' ('t'|'n'|'#'|'\"'|'\\');

WS  :	(' '|'\r'|'\t'|'\u000C'|'\n') {$channel=HIDDEN;};

COMMENT	:   '/*' ( options {greedy=false;} : . )* '*/' {$channel=HIDDEN;};

LINE_COMMENT	: '//' (~('\n'|'\r'))* '\r'? '\n' {$channel=HIDDEN;};

// NUMBER examples:  1 1.2 .3 1.3e10
NUMBER_LITERAL :  (DIGIT)+ ('.' (DIGIT)*)? (('E'|'e') ('+'|'-')? (DIGIT)+)? 
	|	'.' (DIGIT)+ (('E'|'e') ('+'|'-')? (DIGIT)+)? ;

// STRING examples: "foo" "bar"
STRING_LITERAL :  '"' ( ESCAPESEQUENCE | ~('\\'|'"') )* '"';

// HEX examples: #FFFFFF #000fff
HEX_LITERAL : '#' (HEXDIGIT)+;

// IDENTIFIER examples: foo bar foo123
IDENTIFIER //options { testLiterals = true; }
	:	LETTER (LETTER|'0'..'9')*
	;

//#### PARSER ####

program : ( include | expr | func_def )*;

include : 'include' fileName=STRING_LITERAL ';' -> ^('include' STRING_LITERAL);
finally {
  String literal = $fileName.text;
  filesToInclude.add(literal.substring(1, literal.length() - 1));
}

func_def 
	:	'def' name=IDENTIFIER '(' (argument (',' argument)* )? ')' block -> ^(FUNC IDENTIFIER argument* block);

finally {
  functions.put($name.text, $func_def.tree);
}

argument : IDENTIFIER -> ^(ARG IDENTIFIER);

block : '{' expr+ '}' -> ^(SLIST (expr)+);

expr 	: io_expr|assignment_expr|return_expr|math_expr|func_call_expr|flow_stmt;

io_expr	
  : PUTS stmt ';' -> ^(PUTS stmt);

assignment_expr 
	:	assignment_stmt ';'!;

return_expr 
	:	'return' stmt ';' -> ^('return' stmt);

math_expr
	:	add_stmt ';'!; 

func_call_expr
	:	func_call_stmt ';'!;

flow_stmt 
	: iterative_stmt
	| if_stmt
	| stmt;
	
stmt	
  : add_stmt
	| array_or_hash_access
	| constant
	|	array
	| hash
	| built_in_functions
	| enclosed_stmt
	| IDENTIFIER;

built_in_functions
  : file_read_stmt
	| file_write_stmt
	| create_graph_stmt
	| create_chart_stmt
	| split_stmt
	| length_stmt
  | contains_stmt
	| substring_stmt;

split_stmt
  : SPLIT '(' stmt ',' stmt ')' -> ^(SPLIT stmt stmt);

length_stmt
  : LENGTH '(' stmt ')' -> ^(LENGTH stmt);
  
contains_stmt
  : CONTAINS '(' stmt ',' stmt ')' -> ^(CONTAINS stmt stmt);

substring_stmt
  : SUBSTRING '(' stmt ',' stmt ',' stmt ')' -> ^(SUBSTRING stmt stmt stmt);

create_graph_stmt
  : CREATE_GRAPH '(' stmt ')' -> ^(CREATE_GRAPH stmt);

create_chart_stmt
  : CREATE_CHART '(' stmt ')' -> ^(CREATE_CHART stmt);

file_read_stmt
  : READ_FILE '(' stmt ')' -> ^(READ_FILE stmt);

file_write_stmt
  : WRITE_FILE '(' fileName=stmt ',' lines=stmt ',' STRING_LITERAL ')' -> ^(WRITE_FILE $fileName $lines STRING_LITERAL);

enclosed_stmt
  : func_call_stmt
  | logical_stmt;

func_call_stmt	: IDENTIFIER '(' (stmt)?  (',' stmt)* ')' -> ^(CALL IDENTIFIER stmt*);
        	
iterative_stmt
	:	'for'^ '('! iterative_var ';'! logical_stmt ';'! inc_stmt ')'! block
	|	'while' '(' logical_stmt ')' block -> ^('while' logical_stmt block)
	|	'foreach' '(' IDENTIFIER 'in' stmt ')' block -> ^('foreach' IDENTIFIER stmt block);

iterative_var
  : iterative_var_assignment
  | IDENTIFIER;

iterative_var_assignment
  : IDENTIFIER '=' stmt -> ^('=' IDENTIFIER stmt);
	
if_stmt	:	'if' '(' logical_stmt ')' block ( options {greedy=true;}:'else' block)? -> ^('if' logical_stmt block ('else' block)?);

assignment_stmt	
  : IDENTIFIER '=' stmt -> ^('=' IDENTIFIER stmt)
  | inc_stmt
  | array_or_hash_assignment;

array_or_hash_assignment
  : IDENTIFIER '[' k=stmt ']' '=' v=stmt -> ^('=' IDENTIFIER $k $v)
  | array_or_hash_inc_stmt;

array_or_hash_inc_stmt
  : IDENTIFIER '[' k=stmt ']' inc_op v=stmt -> ^(inc_op IDENTIFIER $k $v);

array 
	: '[' ']' -> ^(ARRAY_LIST NIL)
	| '[' (stmt)? (',' stmt)* ']' -> ^(ARRAY_LIST stmt*);

array_or_hash_access
  : IDENTIFIER '[' stmt ']' -> ^(GET_VAL IDENTIFIER stmt);

hash 	: '{' (hash_element)? (',' hash_element)* '}' -> ^(HASH_MAP hash_element*);

hash_element
	: STRING_LITERAL^ '=>'! (stmt|constant|IDENTIFIER);

logical_stmt 
	:	'(' logical_stmt ')' logical_op logical_stmt -> ^(logical_op logical_stmt logical_stmt)
	| logical_val logical_op (logical_stmt | '(' logical_stmt ')') -> ^(logical_op logical_val logical_stmt)
	| logical_val
	| '!' logical_val -> ^(NOT logical_val);

logical_val
  : func_call_stmt|built_in_functions|'true'|'false'|IDENTIFIER|constant;

logical_op : or|and|'=='|'!='|'<'|'>'|'<='|'>=';
or : 'or' -> OR | '||' -> OR;
and : 'and' -> AND | '&&' -> AND;

inc_stmt 
	:	IDENTIFIER inc_op stmt -> ^(inc_op IDENTIFIER stmt);

inc_op 	:	'+='|'-='|'*='|'/=';

add_stmt
	  :	'(' add_stmt ')' add_op add_stmt -> ^(add_op add_stmt add_stmt)
	  | mult_stmt add_op (add_stmt | '(' add_stmt ')') -> ^(add_op mult_stmt add_stmt)
	  | mult_stmt;

add_op : '+'|'-';

mult_stmt 
	  : '(' add_stmt ')' mult_op add_stmt -> ^(mult_op add_stmt add_stmt)
	  | mult_lval mult_op mult_stmt -> ^(mult_op mult_lval mult_stmt)
	  | mult_lval;

mult_lval : func_call_stmt|built_in_functions|constant|IDENTIFIER|array;

mult_op : '*'|'/'|'%';
	 	
constant 
	:	HEX_LITERAL -> ^(HEX HEX_LITERAL)
	|	NUMBER_LITERAL -> ^(NUMBER NUMBER_LITERAL)
	|	STRING_LITERAL -> ^(STRING STRING_LITERAL)
	| 	NIL -> ^(BOOL 'false')
	| 	T -> ^(BOOL 'true')
	| 	F -> ^(BOOL 'false');